package Undo
{
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.TextEvent;
	
	import mx.controls.TextArea;
	import mx.controls.textClasses.TextRange;
	import mx.core.UIComponent;
	
	public class TextAreaUndoAdapter implements IUndoAdapter
	{
		private var myField:TextArea;
		private var undoStack:UndoStack;
		
		private var lastStepDynamic:int;
		private var lastCtrlCode:int;
		private var isInsert:Boolean;
		private var fieldValueBeforeChange:String;

		public static function listenTo(object:UIComponent, stack:UndoStack) : IUndoAdapter
		{
			var field:TextArea = object as TextArea;
			return (field && stack)? new TextAreaUndoAdapter(field, stack) : null;
		}
		
		public function listenTo(object:UIComponent, stack:UndoStack) : IUndoAdapter
		{
			return TextAreaUndoAdapter.listenTo(object, stack);
		}
		
		public function TextAreaUndoAdapter(field:TextArea, stack:UndoStack)
		{
			this.myField = field;
			this.undoStack = stack;
			
			field.addEventListener(FocusEvent.FOCUS_IN, this.saveFieldValue);
			field.addEventListener(FocusEvent.FOCUS_OUT, this.unsaveFieldValue);
			field.addEventListener(KeyboardEvent.KEY_DOWN, this.keyboardListener);
			field.addEventListener(TextEvent.TEXT_INPUT, this.textAreaInput);
			field.addEventListener(Event.CHANGE, this.fieldChange);

			this.lastStepDynamic = 0;
		}
		
		public function unlisten() : void
		{
			this.myField.removeEventListener(FocusEvent.FOCUS_IN, this.saveFieldValue);
			this.myField.removeEventListener(FocusEvent.FOCUS_OUT, this.unsaveFieldValue);
			this.myField.removeEventListener(KeyboardEvent.KEY_DOWN, this.keyboardListener);
			this.myField.removeEventListener(TextEvent.TEXT_INPUT, this.textAreaInput);
			this.myField.removeEventListener(Event.CHANGE, this.fieldChange);
			
			this.undoStack = null;
			this.myField = null;
		}
		
		public function getListened() : UIComponent
		{
			return this.myField;
		}
		
		private function keyboardListener(event:KeyboardEvent) : void
		{
			this.lastCtrlCode = event.ctrlKey? event.charCode : -1;
		}
		
		private function saveFieldValue(event:FocusEvent) : void
		{
			this.fieldValueBeforeChange = myField.text;
			this.isInsert = false;
		}
		
		private function unsaveFieldValue(event:FocusEvent) : void
		{
			this.fieldValueBeforeChange = null;
			this.lastStepDynamic = 0;
		}
		
		private function textAreaInput(event:TextEvent) : void
		{	// text insertion event
			if (event.text.length != 1 || event.text.charCodeAt(0) != this.lastCtrlCode)
			{
				this.isInsert = true;
				var isDynamic:Boolean = (this.lastCtrlCode == -1 && (event.text.length == 1 || /^\n\r$|^\r\n$/.test(event.text)));
				var before:Object, after:Object;
				if (isDynamic && this.lastStepDynamic > 0 && this.lastStepDynamic == myField.selectionBeginIndex && myField.selectionBeginIndex == myField.selectionEndIndex) 
				{
					var prev:UndoEvent = this.undoStack.tryPop();
					before = prev.before;
					after = {beginIndex:prev.after.beginIndex, endIndex:prev.after.endIndex+event.text.length, text:prev.after.text+event.text};
				}
				else 
				{
					before = {beginIndex:myField.selectionBeginIndex, endIndex:myField.selectionEndIndex, text:myField.text.substring(myField.selectionBeginIndex, myField.selectionEndIndex)};
					after = {beginIndex:before.beginIndex, endIndex:before.beginIndex+event.text.length, text:event.text};
				}
				this.undoStack.push(new UndoEvent(this.myField, this, before, after));
				this.lastStepDynamic = isDynamic? after.endIndex : 0;
			}
			else event.preventDefault();
		}
		
		private function fieldChange(event:Event) : void
		{
			if (!this.isInsert)
			{	// text deletion event
				var t:String = this.fieldValueBeforeChange as String;
				var d:int = t.length - this.myField.length;
				var isDynamic:Boolean = d == 1;
				var before:Object, after:Object;
				if (isDynamic && this.lastStepDynamic == -1)
				{
					var prev:UndoEvent = this.undoStack.tryPop();
					before = {beginIndex:prev.before.beginIndex-d, endIndex:prev.before.endIndex, text:t.substr(this.myField.selectionBeginIndex, d)+prev.before.text};
					after = {beginIndex:prev.after.beginIndex-d, endIndex:prev.after.endIndex-d, text:prev.after.text};
				}
				else
				{
					before = {beginIndex:this.myField.selectionBeginIndex, endIndex:this.myField.selectionEndIndex + d, text:t.substr(this.myField.selectionBeginIndex, d)};
					after = {beginIndex:this.myField.selectionBeginIndex, endIndex:this.myField.selectionEndIndex, text:''};
				}
				this.undoStack.push(new UndoEvent(this.myField, this, before, after));
				this.lastStepDynamic = isDynamic? -1 : 0;
			}
			this.saveFieldValue(null);
		}

		public function reverse(after:Object, before:Object) : Boolean
		{
			if (!this.myField.visible || !this.myField.enabled) return false;
			var tr:TextRange = new TextRange(this.myField, true, after.beginIndex, after.endIndex);
			tr.text = before.text;
			this.myField.setSelection(before.beginIndex, before.endIndex);
			this.myField.setFocus();
			this.saveFieldValue(null);
			return true;
		}
	}
}